﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input.Touch;

namespace TPL.Xna.Common
{
    /// <summary>
    /// Base of all sprites
    /// </summary>
    public class Sprite
    {
        // Container screen
        public Screen Screen { get; set; }

        // Drawing properties
        public Vector2 Position;
        public Vector2 Origin;
        public float Scale;
        public float ZIndex;
        public float Angle;
        public Color Color;
        public SpriteEffects Effect;

        /// <summary>
        /// Whether this sprite should be drawn or not.
        /// </summary>
        public bool Visible;

        /// <summary>
        /// Construct with default value.
        /// </summary>
        public Sprite()
        {
            Position = new Vector2(0, 0);
            Scale = 1f;
            Visible = true;
            Color = Color.White;
            MilliSecondsPerFrame = 16;
        }

        /// <summary>
        /// Move this sprite according to given Vector.
        /// </summary>
        /// <param name="direction"></param>
        public void Move(Vector2 direction)
        {
            // Reposition hook
            Position += direction;
            // Reposition hook rect
            if (SpriteArea != null) SpriteArea.Shape.Position += direction;
        }

        #region Event

        /// <summary>
        /// true then tapped event is fire is user tapped on defined area.
        /// </summary>
        public bool HandleTap { get; set; }

        /// <summary>
        /// The area that is defined for being tapped.
        /// </summary>
        public Rectangle TapArea;

        /// <summary>
        /// Event raised when the menu entry is selected.
        /// </summary>
        public event EventHandler<EventArgs> Tapped;
        protected void InvokeTapped(EventArgs e)
        {
            EventHandler<EventArgs> handler = Tapped;
            if (handler != null) handler(this, e);
        }

        /// <summary>
        /// Update the tap area according to its content
        /// </summary>
        protected virtual void UpdateTapArea()
        {
        }

        #endregion

        #region Animators

        private readonly IList<IAnimator> _animators = new List<IAnimator>();

        protected void AddAnimator(IAnimator animator)
        {
            _animators.Add(animator.AttachTo(this));
        }

        #endregion

        #region Collision Implementation

        /// <summary>
        /// Keep a rectangle around the head of hook for detecting collision.
        /// </summary>
        public virtual SpriteArea SpriteArea { get { return null; } }

        /// <summary>
        /// Return true if collision is dectected
        /// </summary>
        /// <returns></returns>
        protected Sprite GetCollisionItem()
        {
            if (SpriteArea != null)
            {
                var collidedObjects = from s in Screen.Sprites
                                      where s != this && s.SpriteArea != null
                                      orderby s.SpriteArea.Shape.Position.Y
                                      where SpriteArea.CollideWith(s.SpriteArea)
                                      select s;
                if (collidedObjects.Count() > 0) return collidedObjects.First();
            }

            return null;
        }

        /// <summary>
        /// Is this sprite out of the screen.
        /// </summary>
        /// <returns></returns>
        protected bool IsOutOfScreen()
        {
            if (SpriteArea == null) return false;
            return SpriteArea.IsOutOfBound(Screen.ScreenManager.GraphicsDevice.Viewport.Bounds);
        }

        #endregion

        #region Basic Loop Implementation

        private int _timeSinceLastFrame;

        /// <summary>
        /// Frame rate.
        /// </summary>
        public decimal MilliSecondsPerFrame { get; set; }

        /// <summary>
        /// Initial content loading.
        /// </summary>
        public virtual void LoadContent()
        {
            if (SpriteArea != null)
            {
                SpriteArea.Shape.ZIndex = ZIndex + 0.1f;
                // Set collision area position
                SpriteArea.Shape.Position = Position - Origin*Scale;
            }

            // Calculate tap area
            if (HandleTap) UpdateTapArea();

            // Let animators a chance to init
            foreach (var animator in _animators)
                animator.Initialize();
        }

        public virtual void UnloadContent()
        {
        }

        /// <summary>
        /// Check input for tapped event if there is a defined area and it is declared as input wanted.
        /// </summary>
        /// <param name="location"></param>
        public virtual void HandleInput(TouchLocation location)
        {
            if (!HandleTap || TapArea == Rectangle.Empty) return;
            switch (location.State)
            {
                case TouchLocationState.Pressed:
                    if (TapArea.Contains(new Point((int)location.Position.X, (int)location.Position.Y)))
                        InvokeTapped(null);
                    break;
                case TouchLocationState.Moved:
                    break;
                case TouchLocationState.Released:
                    break;
            }
        }

        public void Update(GameTime gameTime, Rectangle clientBounds)
        {
            //Update animation frame
            _timeSinceLastFrame += gameTime.ElapsedGameTime.Milliseconds;
            if (_timeSinceLastFrame > MilliSecondsPerFrame)
            {
                _timeSinceLastFrame = 0;

                // TODO: consider the order of internal update and animator update
                InternalUpdate(gameTime, clientBounds);
            }
        }

        protected virtual void InternalUpdate(GameTime gameTime, Rectangle clientBounds)
        {
            if (HandleTap) UpdateTapArea();
            // Let animators a chance to update
            foreach (var animator in _animators.Where(a => a.Enable))
                animator.Update(gameTime);
        }

        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            if (Visible) InternalDraw(gameTime, spriteBatch);
        }

        protected virtual void InternalDraw(GameTime gameTime, SpriteBatch spriteBatch)
        {
        }

        #endregion
    }
}